
#include <BOpenGL>
#include <BRender>

#include <BGeometry>
#include "member_BGeometry.h"
#include "member_BElement.h"
#include "member_BTexture.h"
#include "member_BMesh.h"
#include "member_BSurface.h"
#include "member_BTerrain.h"

using namespace BWE;

#define member					(*(member_BGeometry*)_ptr)
#define member_allocate()		_ptr = new member_BGeometry(this)
#define member_release()		delete (member_BGeometry*)_ptr

BGeometry::BGeometry()
{
	member_allocate();
}
BGeometry::BGeometry(const BString& name)
{
	member_allocate();
	this->setName(name);
}
BGeometry::BGeometry(const BGeometry& other) : BShape(other)
{
	member_allocate();
	this->setName(other.name());
	this->copy(&other);
}
BGeometry::~BGeometry()
{
	member_release();
}

void BGeometry::copy(const BShape* other)
{
	BShape::copy(other);
	if (const BGeometry* geometry = dynamic_cast<const BGeometry*>(other))
	{
		geometry->flush();
		member.vertices = geometry_member(geometry)->vertices;
		member.normals = geometry_member(geometry)->normals;
		member.colors = geometry_member(geometry)->colors;
		member.coords = geometry_member(geometry)->coords;
		for (int i = 0; i < geometry_member(geometry)->elements.size(); i++)
		{
			BElement* element = geometry_member(geometry)->elements[i]->clone();
			this->addElement(element);
		}
	}
	if (const BMesh* mesh = dynamic_cast<const BMesh*>(other))
	{
		mesh->flush();
		member.vertices = mesh_member(mesh)->vertices;
		member.normals = mesh_member(mesh)->normals;
		member.colors = mesh_member(mesh)->colors;
		member.coords = mesh_member(mesh)->coords;
		if (const BFaceArray* faces = mesh_member(mesh)->faces)
		{
			BIntArray* indices = new BIntArray(faces->size() * 3);
			for (int i = 0; i < faces->size(); i++)
			{
				const BFace& face = (*faces)[i];
				indices->append(face.a());
				indices->append(face.b());
				indices->append(face.c());
			}
			this->addElement(Primitive_Triangles, indices);
		}
		else
		{
			this->addElement(Primitive_Triangles, 0, member.vertices->size());
		}
	}
	if (const BSurface* surface = dynamic_cast<const BSurface*>(other))
	{
		surface->flush();
		member.vertices = surface_member(surface)->vertices;
		member.normals = surface_member(surface)->normals;
		member.colors = surface_member(surface)->colors;
		member.coords.clear();
		if (BCoordArray* coords = surface_member(surface)->coords)
		{
			member.coords.append(coords);
		}
		BIntArray* indices = new BIntArray(*surface->indices());
		this->addElement(Primitive_Quads, indices);
	}
	if (const BTerrain* terrain = dynamic_cast<const BTerrain*>(other))
	{
		terrain->flush();
		member.vertices = terrain_member(terrain)->vertices;
		member.normals = terrain_member(terrain)->normals;
		member.colors = terrain_member(terrain)->colors;
		member.coords.clear();
		if (BCoordArray* coords = terrain_member(terrain)->coords)
		{
			member.coords.append(coords);
		}
		BIntArray* indices = new BIntArray(*terrain->indices());
		this->addElement(Primitive_Quads, indices);
	}
	if (member.polyOctTree)
	{
		member.freshPolyTree();
	}
}
BShape* BGeometry::clone() const
{
	return new BGeometry(*this);
}

void BGeometry::setOptimized(bool optimized)
{
	if (optimized && !member.polyOctTree)
	{
		member.polyOctTree = new PolyOctTree();
		member.freshPolyTree();
		return;
	}
	if (!optimized && member.polyOctTree)
	{
		member.polyOctTree = 0;
		return;
	}
}
bool BGeometry::optimized() const
{
	return !member.polyOctTree.empty();
}

void BGeometry::setVertices(BVectorArray* vertices)
{
	member.vertices = vertices;
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		element_member(element)->dirtyList = true;
	}
	this->dirty();
}
BVectorArray* BGeometry::vertices()
{
	return member.vertices;
}
const BVectorArray* BGeometry::vertices() const
{
	return member.vertices;
}

void BGeometry::setNormals(BVectorArray* normals)
{
	member.normals = normals;
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		element_member(element)->dirtyList = true;
	}
	emit(Signal_Freshed);
}
BVectorArray* BGeometry::normals()
{
	return member.normals;
}
const BVectorArray* BGeometry::normals() const
{
	return member.normals;
}

void BGeometry::setColors(BColorArray* colors)
{
	member.colors = colors;
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		element_member(element)->dirtyList = true;
	}
	emit(Signal_Freshed);
}
BColorArray* BGeometry::colors()
{
	return member.colors;
}
const BColorArray* BGeometry::colors() const
{
	return member.colors;
}

bool BGeometry::setTexCoords(BCoordArray* texCoords, int unit)
{
	if (unit > 30)
		return false;
	if (member.coords.size() < unit + 1)
		member.coords.resize(unit + 1);
	member.coords[unit] = texCoords;
	for (int i = member.coords.size(); i > 0; i--)
	{
		if (member.coords[i - 1])
		{
			member.coords.remove(i, member.coords.size());
			break;
		}
	}
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		element_member(element)->dirtyList = true;
	}
	emit(Signal_Freshed);
	return true;
}
const BCoordArray* BGeometry::texCoords(int unit) const
{
	return member.coords(unit);
}
BCoordArray* BGeometry::texCoords(int unit)
{
	return member.coords(unit);
}
bool BGeometry::clearTexCoords()
{
	if (member.coords.clear())
	{
		emit(Signal_Freshed);
		return true;
	}
	return false;
}

BElement* BGeometry::addElement(Primitive primitive, int first, int count)
{
	BElement* element = new BElement(primitive, first, count);
	addElement(element);
	return element;
}
BElement* BGeometry::addElement(Primitive primitive, BIntArray* indices)
{
	BElement* element = new BElement(primitive, indices);
	addElement(element);
	return element;
}

bool BGeometry::addElement(BElement* element)
{
	if (!element)
		return false;
	if (element_member(element)->geometry == this)
		return true;
	BHolder<BElement> holder(element);
	if (element_member(element)->geometry)
		element_member(element)->geometry->removeElement(element);
	member.elements.append(element);
	element_member(element)->dirtyList = true;
	element_member(element)->geometry = this;
	emit(Signal_Attached, element);
	emit(Signal_Freshed);
	this->dirty();
	return true;
}
bool BGeometry::removeElement(const BElement* element)
{
	if (!element || element_member(element)->geometry != this)
		return false;
	element_member(element)->geometry = 0;
	member.elements.remove(element);
	emit(Signal_Detached, element);
	emit(Signal_Freshed);
	return true;
}
bool BGeometry::replaceElement(BElement* element, BElement* newElement)
{
	if (!element)
		return false;
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* shape = member.elements[i];
		if (shape == element)
		{
			BElementHolder holder(shape);
			if (newElement)
			{
				member.elements[i] = newElement;
			}
			else
			{
				member.elements.remove(i);
			}
			if (element)
			{
				this->emit(Signal_Remove, BValue(element, this));
			}
			if (newElement)
			{
				this->emit(Signal_Insert, BValue(newElement, this));
			}
			this->dirty();
			return true;
		}
	}
	return false;
}
bool BGeometry::clearElements()
{
	if (member.elements.size())
	{
		while (BElement* element = member.elements.last())
		{
			BHolder<BElement> holder(element);
			element_member(element)->geometry = 0;
			member.elements.remove(element);
			this->emit(Signal_Detached, element);
		}
		member.elements.clear();
		this->emit(Signal_Freshed);
		return true;
	}
	return false;
}

int BGeometry::elementCount() const
{
	return member.elements.size();
}
BElement* BGeometry::element(int index)
{
	return member.elements(index);
}
const BElement* BGeometry::element(int index) const
{
	return member.elements(index);
}

BElement* BGeometry::element(const BString& name)
{
	for (int i = 0; i < member.elements.size(); i++)
	{
		if (member.elements[i]->name() == name)
			return member.elements[i];
	}
	return 0;
}
const BElement* BGeometry::element(const BString& name) const
{
	for (int i = 0; i < member.elements.size(); i++)
	{
		if (member.elements[i]->name() == name)
			return member.elements[i];
	}
	return 0;
}

bool BGeometry::empty() const
{
	return member.vertices == 0 && member.elements.empty();
}
void BGeometry::smooth()
{
	BVectorArray* vertices = member.vertices;
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != vertices->size())
	{
		normals = new BVectorArray(vertices->size());
		this->setNormals(normals);
	}
	normals->fill(BVector());
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		int first = element->first();
		int count = element->count();
		if (BIntArray* indices = element->indices())
		{
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < count; i += 3)
				{
					unsigned int ia = (*indices)[first + i];
					unsigned int ib = (*indices)[first + i + 1];
					unsigned int ic = (*indices)[first + i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < count; i += 4)
				{
					unsigned int ia = (*indices)[first + i];
					unsigned int ib = (*indices)[first + i + 1];
					unsigned int ic = (*indices)[first + i + 2];
					unsigned int id = (*indices)[first + i + 3];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
					(*normals)[id] += normal;
				}
			}
		}
		else
		{
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < count; i += 3)
				{
					unsigned int ia = first + i;
					unsigned int ib = first + i + 1;
					unsigned int ic = first + i + 2;
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < count; i += 4)
				{
					unsigned int ia = first + i;
					unsigned int ib = first + i + 1;
					unsigned int ic = first + i + 2;
					unsigned int id = first + i + 3;
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
					(*normals)[id] += normal;
				}
			}
		}
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i].normalize();
	}
	this->setNormals(normals);
}
void BGeometry::reverse()
{
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		int first = element->first();
		int count = element->count();
		if (BIntArray* indices = element->indices())
		{
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < count; i += 3)
				{
					int& ia = (*indices)[first + i];
					int& ib = (*indices)[first + i + 1];
					bSwap(ia, ib);
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < count; i += 4)
				{
					int& ib = (*indices)[first + i + 1];
					int& id = (*indices)[first + i + 3];
					bSwap(ib, id);
				}
			}
		}
		if (BPieceArray* pieces = element->pieces())
		{
			element_member(element)->dirtyList = true;
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < pieces->size(); i += 3)
				{
					BPiece& pa = (*pieces)[i];
					BPiece& pb = (*pieces)[i + 1];
					bSwap(pa, pb);
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < pieces->size(); i += 4)
				{
					BPiece& pb = (*pieces)[first + i + 1];
					BPiece& pd = (*pieces)[first + i + 3];
					bSwap(pb, pd);
				}
			}
		}
	}
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != this->vertices()->size())
	{
		smooth();
		return;
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i] = -(*normals)[i];
	}
	this->fresh();
}
void BGeometry::simplify()
{
	if (member.vertices && member.elements.size())
	{
		BArray<int> indexStats(-1, member.vertices->size());
		for (int i = 0; i < member.elements.size(); i++)
		{
			BElement* element = member.elements[i];
			if (BIntArray* indices = element->indices())
			{
				for (int i = 0; i < indices->size(); i++)
				{
					int index = indices->at(i);
					indexStats[index] = index;
				}
			}
			else if (BPieceArray* pieces = element->pieces())
			{
				for (int i = 0; i < pieces->size(); i++)
				{
					const BPiece& piece = pieces->at(i);
					indexStats[piece.v()] = piece.v();
				}
			}
		}
		BVectorArray* vertices = new BVectorArray();
		BVectorArray* normals = 0;
		if (member.normals)
			normals = new BVectorArray();
		BColorArray* colors = 0;
		if (member.colors)
			colors = new BColorArray();
		BCoordArray* coords = 0;
		if (member.coords.size())
			coords = new BCoordArray();
		for (int i = 0; i < indexStats.size(); i++)
		{
			int index = indexStats[i];
			if (index >= 0)
			{
				indexStats[i] = vertices->size();
				vertices->append(member.vertices->at(index));
				if (normals)
					normals->append(member.normals->at(index));
				if (colors)
					colors->append(member.colors->at(index));
				if (coords)
					coords->append(member.coords[0]->at(index));
			}
		}
		member.vertices = vertices;
		if (normals)
			member.normals = normals;
		if (colors)
			member.colors = colors;
		if (coords)
			member.coords[0] = coords;
		for (int i = 0; i < member.elements.size(); i++)
		{
			BElement* element = member.elements[i];
			if (BIntArray* indices = element->indices())
			{
				for (int i = 0; i < indices->size(); i++)
				{
					int index = (*indices)[i];
					(*indices)[i] = indexStats[index];
				}
			}
			else if (BPieceArray* pieces = element->pieces())
			{
				for (int i = 0; i < pieces->size(); i++)
				{
					BPiece& piece = pieces->at(i);
					piece.v() = indexStats[piece.v()];
				}
			}
		}
		this->dirty();
	}
}

bool BGeometry::flush() const
{
	if (BShape::flush())
	{
		if (member.polyOctTree)
			member.freshPolyTree();
		if (member.elements.size())
			member.freshElementBoxes();
		return true;
	}
	return false;
}
void BGeometry::clear()
{
	member.vertices = 0;
	member.normals = 0;
	member.colors = 0;
	member.coords.clear();
	member.elements.clear();
	this->dirty();
}

bool BGeometry::query(const BString& text, BValue& value) const
{
	if (text == "polytree")
	{
		value = member.polyOctTree;
		return true;
	}
	if (text == "vertices")
	{
		value = member.vertices;
		return true;
	}
	return BShape::query(text, value);
}

BVector BGeometry::inertia(BReal mass) const
{
	const BBox& box = this->box();
	BVector dimension = box.veca() + box.vecb() + box.vecc();

	BReal lx = 2 * dimension.x();
	BReal ly = 2 * dimension.y();
	BReal lz = 2 * dimension.z();

	BReal inex = mass / 12 * (ly * ly + lz * lz);
	BReal iney = mass / 12 * (lx * lx + lz * lz);
	BReal inez = mass / 12 * (lx * lx + ly * ly);

	return BVector(inex, iney, inez);
}
BVector BGeometry::vertex(const BVector& direct) const
{
	struct SupportingCallback : public ITriangleCallback
	{
		const BVector& direct;
		BLine line;
		BReal maxRatio = 0;
		int count = 0;
		SupportingCallback(const BVector& dir, BReal length) : direct(dir)
		{
			line.setEnd(direct * length * 2);
		}
		virtual void process(const BTriangle& triangle, int triangleIndex)
		{
			count++;
			BReal ratio;
			if (line.intersect(triangle, ratio, true))
			{
				if (ratio > maxRatio)
				{
					maxRatio = ratio;
				}
			}
		}
	};
	SupportingCallback callback(direct, this->box().radius());
	member.overlapping(&callback, callback.line);
	return callback.line.vertex(callback.maxRatio);
}

void BGeometry::refresh(BBox& box)
{
	member.localSpace.reset();
	if (member.vertices && member.vertices->size())
	{
		member.localSpace.reset(member.vertices->first());
		for (int i = 1; i < member.vertices->size(); i++)
		{
			member.localSpace.expand(member.vertices->at(i));
		}
	}
	box = member.localSpace;
}
void BGeometry::render(BRender& render)
{
	if (!member.vertices || member.vertices->empty() || member.elements.empty())
		return;
	if (member.vertices)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		BOpenGL::glVertexArray(member.vertices);
	}
	if (member.normals)
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		BOpenGL::glNormalArray(member.normals);
	}
	if (member.colors)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_UNSIGNED_BYTE, 0, member.colors->data());
	}
	for (int i = 0; i < member.coords.size(); i++)
	{
		if (member.coords[i] && this->texture(i))
		{
			glClientActiveTexture(GL_TEXTURE0 + i);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(2, GL_FLOAT, 0, member.coords[i]->data());
		}
	}
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		if (element_member(element)->visible)
		{
			GLenum mode = 0;
			switch (element->primitive())
			{
			case Primitive_Points:			mode = GL_POINTS;			break;
			case Primitive_Lines:			mode = GL_LINES;			break;
			case Primitive_LineStrip:		mode = GL_LINE_STRIP;		break;
			case Primitive_LineLoop:		mode = GL_LINE_LOOP;		break;
			case Primitive_Triangles:		mode = GL_TRIANGLES;		break;
			case Primitive_TriangleStrip:	mode = GL_TRIANGLE_STRIP;	break;
			case Primitive_TriangleFan:		mode = GL_TRIANGLE_FAN;		break;
			case Primitive_Quads:			mode = GL_QUADS; 			break;
			case Primitive_QuadStrip:		mode = GL_QUAD_STRIP;		break;
			case Primitive_Polygon:			mode = GL_POLYGON;			break;
			default:
				continue;
			}
			if (BIntArray* indices = element->indices())
			{
				glEnableClientState(GL_INDEX_ARRAY);
				glDrawElements(mode, indices->size(), GL_UNSIGNED_INT, indices->data());
				glDisableClientState(GL_INDEX_ARRAY);
			}
			else if (BPieceArray* pieces = element->pieces())
			{
				member.drawPiecesLObject(element, mode);
			}
			else
			{
				glDrawArrays(mode, element->first(), element->count());
			}
		}
	}
	if (member.vertices)
	{
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	if (member.normals)
	{
		glDisableClientState(GL_NORMAL_ARRAY);
	}
	if (member.colors)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	for (int i = 0; i < member.coords.size(); i++)
	{
		if (member.coords[i] && this->texture(i))
		{
			glClientActiveTexture(GL_TEXTURE0 + i);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}
	}
}
